package com.golang.api.util;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.Base64Variant;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.PrettyPrinter;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler;
import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.ser.FilterProvider;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import lombok.Getter;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.io.Serializable;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;



@Slf4j
public class JsonUtils {
    private static final DateFormat DATE_TIME = new SimpleDateFormat(DateTimeFormatterEnum.DATE_TIME.getPattern());
    /**
     * 互联互通字段首字母大写、ID两个字母全大写，注意对Map无效
     *
     * @return
     */
    public static final JsonMapper hlht = builder()
            .defaultConfig()
            .setPropertyNamingStrategy(new PropertyNamingStrategy.UpperCamelCaseStrategy(){
                @Override
                public String translate(String input) {
                    String name = super.translate(input);
                    if (name.endsWith("Id")) {
                        return name.substring(0, name.length() - 1) + "D";
                    }
                    return name;
                }
            })
            .build();

    /**
     * 使用默认配置
     */
    public static final JsonMapper normal = builder()
            .defaultConfig()
            .build();

    /**
     * 忽略null值
     */
    public static final JsonMapper ignoreNull = builder()
            .defaultConfig()
            .setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL)
            .build();

    public static Integer getIntValue(JsonNode node, String key) {
      return getIntValue(node, key, null);
    }

    public static Integer getIntValue(JsonNode node, String key, Integer defaultValue) {
      return Optional.ofNullable(node).map(n -> n.get(key)).map(n -> n.asInt()).orElse(defaultValue);
    }

    public static String getStrValue(JsonNode node, String key) {
      return getStrValue(node, key, null);
    }

    public static String getStrValue(JsonNode node, String key, String defaultValue) {
      return Optional.ofNullable(node).map(n -> n.get(key)).map(n -> n.asText()).orElse(defaultValue);
    }

    public static Long getLongValue(JsonNode node, String key) {
      return getLongValue(node, key, null);
    }

    public static Long getLongValue(JsonNode node, String key, Long defaultValue) {
      return Optional.ofNullable(node).map(n -> n.get(key)).map(n -> n.asLong()).orElse(defaultValue);
    }

    public static Double getDoubleValue(JsonNode node, String key) {
      return getDoubleValue(node, key, null);
    }

    public static Double getDoubleValue(JsonNode node, String key, Double defaultValue) {
      return Optional.ofNullable(node).map(n -> n.get(key)).map(n -> n.asDouble()).orElse(defaultValue);
    }

    public static BigDecimal getBigDecimalValue(JsonNode node, String key) {
      return getBigDecimalValue(node, key, null);
    }

    public static BigDecimal getBigDecimalValue(JsonNode node, String key, BigDecimal defaultValue) {
      String value = getStrValue(node, key);
      return ObjectUtils.obj2BigDecimal(value, defaultValue);
    }

    /**
     * 利用Jackson的序列化和反序列化实现bean对象的复制，相比spring-beans的BeanUtils.copyPorperties，可以达到深度复制的目的
     * @param src 需要复制的对象
     * @param target 目标类型的Class对象
     * @param <T> 目标类型
     * @return
     */
    public static <T> T convert(Serializable src, Class<T> target){
        return normal.toObject(normal.toJson(src), target);
    }

    public static ObjectNode createObjectNode(){
        return normal.createObjectNode();
    }

    public static ArrayNode createArrayNode(){
        return normal.createArrayNode();
    }

    public static String toJson(Object object) {
        return normal.toJson(object);
    }

    public static JsonNode toJsonNode(String json) {
        return normal.toJsonNode(json);
    }

    public static <T> T toObject(String json, @NonNull Class<T> clazz) {
        return normal.toObject(json, clazz);
    }

    public static <T> List<T> toList(String json, @NonNull Class<T> clazz) {
        return normal.toList(json, clazz);
    }

    public static <K,V> Map<K,V> toMap(String json, @NonNull Class<K> keyClass, @NonNull Class<V> valueClass) {
        return normal.toMap(json, keyClass, valueClass);
    }

    private static <T> T toObject(String json, JavaType javaType) {
        return normal.toObject(json, javaType);
    }

    public static JsonMapperBuilder builder() {
        return new JsonMapperBuilder(new ObjectMapper());
    }

    public static class JsonMapperBuilder {
        private ObjectMapper objectMapper;

        public JsonMapperBuilder(ObjectMapper objectMapper) {
            this.objectMapper = objectMapper;
        }

        public JsonMapper build() {
            return new JsonMapper(objectMapper);
        }

        /**
         * 默认加下列配置
         * 反序列化时忽略空值
         * Date和LocalDateTime系列的日期格式
         */
        public JsonMapperBuilder defaultConfig() {
            return configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
                    .setDateFormat(DATE_TIME)
                    .registDateTimeModule();
        }

        public JsonMapperBuilder setAnnotationIntrospector(AnnotationIntrospector ai) {
            objectMapper.setAnnotationIntrospector(ai);
            return this;
        }

        public JsonMapperBuilder setAnnotationIntrospectors(AnnotationIntrospector serializerAI, AnnotationIntrospector deserializerAI) {
            objectMapper.setAnnotationIntrospectors(serializerAI, deserializerAI);
            return this;
        }

        public JsonMapperBuilder setPropertyNamingStrategy(PropertyNamingStrategy s) {
            objectMapper.setPropertyNamingStrategy(s);
            return this;
        }

        public JsonMapperBuilder setDefaultPrettyPrinter(PrettyPrinter pp) {
            objectMapper.setDefaultPrettyPrinter(pp);
            return this;
        }

        public JsonMapperBuilder setSerializationInclusion(JsonInclude.Include incl) {
            objectMapper.setSerializationInclusion(incl);
            return this;
        }

        public JsonMapperBuilder setDefaultPropertyInclusion(JsonInclude.Value incl) {
            objectMapper.setDefaultPropertyInclusion(incl);
            return this;
        }

        public JsonMapperBuilder setDefaultPropertyInclusion(JsonInclude.Include incl) {
            objectMapper.setDefaultPropertyInclusion(JsonInclude.Value.construct(incl, incl));
            return this;
        }

        public JsonMapperBuilder setDefaultSetterInfo(com.fasterxml.jackson.annotation.JsonSetter.Value v) {
            objectMapper.setDefaultSetterInfo(v);
            return this;
        }

        public JsonMapperBuilder setDefaultVisibility(com.fasterxml.jackson.annotation.JsonAutoDetect.Value vis) {
            objectMapper.setDefaultVisibility(vis);
            return this;
        }

        public JsonMapperBuilder setDefaultMergeable(Boolean b) {
            objectMapper.setDefaultMergeable(b);
            return this;
        }

        public JsonMapperBuilder enableDefaultTyping() {
            return this.enableDefaultTyping(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
        }

        public JsonMapperBuilder enableDefaultTyping(ObjectMapper.DefaultTyping dti) {
            return this.enableDefaultTyping(dti, JsonTypeInfo.As.WRAPPER_ARRAY);
        }

        public JsonMapperBuilder enableDefaultTyping(ObjectMapper.DefaultTyping applicability, JsonTypeInfo.As includeAs) {
            objectMapper.enableDefaultTyping(applicability, includeAs);
            return this;
        }

        public JsonMapperBuilder enableDefaultTypingAsProperty(ObjectMapper.DefaultTyping applicability, String propertyName) {
            objectMapper.enableDefaultTypingAsProperty(applicability, propertyName);
            return this;
        }

        public JsonMapperBuilder disableDefaultTyping() {
            return this.setDefaultTyping(null);
        }

        public JsonMapperBuilder setDefaultTyping(TypeResolverBuilder<?> typer) {
            objectMapper.setDefaultTyping(typer);
            return this;
        }

        public JsonMapperBuilder setTypeFactory(TypeFactory f) {
            objectMapper.setTypeFactory(f);
            return this;
        }

        public JsonMapperBuilder setNodeFactory(JsonNodeFactory f) {
            objectMapper.setNodeFactory(f);
            return this;
        }

        public JsonMapperBuilder addHandler(DeserializationProblemHandler h) {
            objectMapper.addHandler(h);
            return this;
        }

        public JsonMapperBuilder clearProblemHandlers() {
            objectMapper.clearProblemHandlers();
            return this;
        }

        public JsonMapperBuilder setConfig(DeserializationConfig config) {
            objectMapper.setConfig(config);
            return this;
        }

        public JsonMapperBuilder setFilterProvider(FilterProvider filterProvider) {
            objectMapper.setFilterProvider(filterProvider);
            return this;
        }

        public JsonMapperBuilder setBase64Variant(Base64Variant v) {
            objectMapper.setBase64Variant(v);
            return this;
        }

        public JsonMapperBuilder setConfig(SerializationConfig config) {
            objectMapper.setConfig(config);
            return this;
        }

        public JsonMapperBuilder setDateFormat(DateFormat dateFormat) {
            objectMapper.setDateFormat(dateFormat);
            return this;
        }

        public JsonMapperBuilder setInjectableValues(InjectableValues injectableValues) {
            objectMapper.setInjectableValues(injectableValues);
            return this;
        }

        public JsonMapperBuilder setLocale(Locale l) {
            objectMapper.setLocale(l);
            return this;
        }

        public JsonMapperBuilder setTimeZone(TimeZone tz) {
            objectMapper.setTimeZone(tz);
            return this;
        }

        public JsonMapperBuilder configure(DeserializationFeature f, boolean state) {
            objectMapper.configure(f, state);
            return this;
        }

        public JsonMapperBuilder configure(SerializationFeature f, boolean state) {
            objectMapper.configure(f, state);
            return this;
        }

        public JsonMapperBuilder registerModule(Module module) {
            objectMapper.registerModule(module);
            return this;
        }

        public JsonMapperBuilder registDateTimeModule(){
            SimpleModule module = new SimpleModule()
                    .addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatterEnum.DATE_TIME.getFormatter()))
                    .addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatterEnum.DATE.getFormatter()))
                    .addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatterEnum.TIME.getFormatter()))
                    .addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatterEnum.DATE_TIME.getFormatter()))
                    .addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatterEnum.DATE.getFormatter()))
                    .addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatterEnum.TIME.getFormatter()));
            return this.registerModule(module);
        }

    }

    public static class JsonMapper {
        private ObjectMapper objectMapper;

        JsonMapper(ObjectMapper objectMapper) {
            this.objectMapper = objectMapper;
        }

        public ObjectNode createObjectNode(){
            return objectMapper.createObjectNode();
        }

        public ArrayNode createArrayNode(){
            return objectMapper.createArrayNode();
        }

        public String toJson(Object object) {
            if (object == null) {
                return null;
            }
            try {
                return objectMapper.writeValueAsString(object);
            } catch (JsonProcessingException e) {
                log.error("object转json失败, obj: " + object.toString(), e);
                return null;
            }
        }

        public JsonNode toJsonNode(String json) {
            if (StringUtils.isBlank(json)) {
                return null;
            }
            try {
                return objectMapper.readTree(json);
            } catch (IOException e) {
                log.error("json转JsonNode失败, json: " + json, e);
                return null;
            }
        }

        public <T> T toObject(String json, @NonNull Class<T> clazz) {
            if (StringUtils.isBlank(json)) {
                return null;
            }
            return this.toObject(json, this.objectMapper.constructType(clazz));
        }

        public <T> List<T> toList(String json, @NonNull Class<T> clazz) {
            if (StringUtils.isBlank(json)) {
                return null;
            }
            return this.toObject(json, this.objectMapper.getTypeFactory().constructCollectionType(List.class, clazz));
        }

        public <K,V> Map<K,V> toMap(String json, @NonNull Class<K> keyClass, @NonNull Class<V> valueClass) {
            if (StringUtils.isBlank(json)) {
                return null;
            }
            return this.toObject(json, this.objectMapper.getTypeFactory().constructMapType(Map.class, keyClass, valueClass));
        }

        private <T> T toObject(String json, JavaType javaType) {
            if (StringUtils.isBlank(json) || javaType == null) {
                return null;
            }
            try {
                return objectMapper.readValue(json, javaType);
            } catch (IOException e) {
                log.error("json转object失败, json: " + json + ", class: " + javaType.getTypeName(), e);
                return null;
            }
        }
    }

    public static void main(String[] args) {
        JsonMapper jsonMapper = JsonUtils.normal;
        String s = jsonMapper.toJson(LocalDateTime.now());
        Date time = convert(LocalDateTime.now(), Date.class);
        log.info("LocalDateTime to Date", time);
        LocalDateTime localDateTime = convert(time, LocalDateTime.class);
        log.info("Date to LocalDateTime", localDateTime);
        log.info("转换LocalDateTime: {}", s);
        LocalDateTime date = jsonMapper.toObject(s, LocalDateTime.class);
        log.info("解析LocalDateTime: {}", date);
        Object obj = new Object(){
            @Getter Integer objId = 1;
            @Getter String objName = "aa";
            @Getter Integer objAge = null;

            @Override
            public String toString() {
                return String.join(" ", "objId: ", String.valueOf(objId), "objName: ", objName, "objAge: ", String.valueOf(objAge));
            }
        };
        String json = jsonMapper.toJson(obj);
        log.info("转换Object: {}", json);
        log.info("hlht转换Object: {}", JsonUtils.hlht.toJson(obj));
        log.info("忽略Null转换Object: {}", JsonUtils.ignoreNull.toJson(obj));
        log.info("解析Object（将报错，不能反序列化匿名类）: {}", jsonMapper.toObject(json, obj.getClass()));
        log.info("解析Object成JsonNode: {}", jsonMapper.toJsonNode(json));
        HashMap<String, Object> map = new HashMap<>();
        map.put("k1", "v1");
        map.put("k2", 2);
        map.put("k3", null);
        String mapJson = jsonMapper.toJson(map);
        log.info("转换Map: {}", mapJson);
        log.info("hlht转换Object: {}", JsonUtils.hlht.toJson(map));
        log.info("忽略Null转换Object: {}", JsonUtils.ignoreNull.toJson(map));
        log.info("解析Map: {}", jsonMapper.toMap(mapJson, String.class, Object.class));
        log.info("解析Map到JsonNode: {}", jsonMapper.toJsonNode(mapJson));
        List<String> list = new ArrayList<>();
        list.add("1");
        list.add("2");
        list.add("3");
        String listJson = jsonMapper.toJson(list);
        log.info("转换list: {}", listJson);
        log.info("解析List: {}", jsonMapper.toList(listJson, String.class));
        log.info("解析List到JsonNode: {}", jsonMapper.toJsonNode(listJson));
        ObjectNode objectNode = jsonMapper.toObject(mapJson, ObjectNode.class);
        objectNode.put("k4", "v4");
        log.info("解析map到ObjectNode: {}", objectNode);
        ArrayNode arrayNode = jsonMapper.toObject(listJson, ArrayNode.class);
        log.info("解析list到ArrayNode: {}", arrayNode);

    }
}
